﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;

namespace ShevaEngine.Core.Modules.Graphics
{
    /// <summary>
    /// Graphics buffers.
    /// </summary>
    public class GraphicsBuffers<T> : IGraphicsBuffers
        where T : struct, IVertexType
    {
        /// <summary>Vertices.</summary>
        private List<List<T>> IVertexTypeVertices;
        /// <summary>Vertices.</summary>
        private List<Int32> IVertexTypeVerticesOffsets;
        /// <summary>Vertices.</summary>
        private List<Int32> IVertexTypeVerticesElementsCount;
        /// <summary>Vertices.</summary>
        private List<Byte[]> BytesVertices;
        /// <summary>Vertices.</summary>
        private List<Int32> BytesVerticesOffsets;
        /// <summary>Vertices.</summary>
        private List<Int32> BytesVerticesElementsCount;
        /// <summary>Vertices.</summary>
        private List<Int32> Indices;
        /// <summary>Vertex buffer.</summary>
        public VertexBuffer VBO { get; private set; }
        /// <summary>Index buffer.</summary>
        public IndexBuffer IBO { get; private set; }

        /// <summary>Vertices count.</summary>
        private Int32 VerticesCount = 0;

        /// <summary>Max VBO length.</summary>
        private Int32 MaxVBOLength = 700000;
        /// <summary>Max IBO length.</summary>
        private Int32 MaxIBOLength = 700000;

        /// <summary>Vertex declaration.</summary>
        public VertexDeclaration VertexDeclaration { get; private set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="declaration">Vertex declaration.</param>
        public GraphicsBuffers(VertexDeclaration vertexDeclaration)
        {
            this.VertexDeclaration = vertexDeclaration;

            this.IVertexTypeVertices = new List<List<T>>();
            this.IVertexTypeVerticesOffsets = new List<int>();
            this.IVertexTypeVerticesElementsCount = new List<int>();

            this.BytesVertices = new List<byte[]>();
            this.BytesVerticesOffsets = new List<int>();
            this.BytesVerticesElementsCount = new List<int>();

            this.Indices = new List<int>();
        }


        /// <summary>
        /// Method tests if we can add
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="vertices"></param>
        /// <param name="indices"></param>
        public Boolean CanAddData(DrawCall drawCall, VertexDeclaration vertexDeclaration, List<Int32> indices)
        {
            if (vertexDeclaration == this.VertexDeclaration &&
                drawCall.NumVertices + this.VerticesCount <= this.MaxVBOLength &&
                indices.Count + this.Indices.Count <= this.MaxIBOLength)               
                return true;

            return false;
        }

        /// <summary>
        /// Method adds data.
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="vertices"></param>
        /// <param name="indices"></param>
        public void AddData(DrawCall drawCall, List<IVertexType> vertices, List<Int32> indices)
        {
            drawCall.BaseVertex = this.VerticesCount;
            drawCall.StartIndex = this.Indices.Count;

            List<T> tempList = new List<T>();

            foreach (IVertexType item in vertices)
                tempList.Add((T)item);

            this.IVertexTypeVerticesOffsets.Add(this.VerticesCount);
            this.IVertexTypeVerticesElementsCount.Add(drawCall.NumVertices);
            this.IVertexTypeVertices.Add(tempList);            

            this.Indices.AddRange(indices);
            this.VerticesCount += drawCall.NumVertices;
        }

        /// <summary>
        /// Method adds data.
        /// </summary>
        /// <param name="drawCall"></param>
        /// <param name="verticesData"></param>
        /// <param name="indices"></param>
        public void AddData(DrawCall drawCall, byte[] verticesData, List<int> indices)
        {
            drawCall.BaseVertex = this.VerticesCount;
            drawCall.StartIndex = this.Indices.Count;

            this.BytesVerticesOffsets.Add(this.VerticesCount);
            this.BytesVerticesElementsCount.Add(drawCall.NumVertices);
            this.BytesVertices.Add(verticesData);    

            this.Indices.AddRange(indices);
            this.VerticesCount += drawCall.NumVertices;
        }

        /// <summary>
        /// Method creates buffers.
        /// </summary>
        public void CreateBuffers()
        {
            this.VBO = new VertexBuffer(ShevaEngine.Instance.GraphicsDevice, this.VertexDeclaration, this.IVertexTypeVertices.Count, BufferUsage.WriteOnly);

            for (int i = 0; i < this.IVertexTypeVertices.Count; i++)            
                this.VBO.SetData<T>(this.IVertexTypeVertices[i].ToArray(), this.IVertexTypeVerticesOffsets[i], this.IVertexTypeVerticesElementsCount[i]);

            for (int i = 0; i < this.BytesVertices.Count; i++)
                this.VBO.SetData<Byte>(this.BytesVertices[i], this.BytesVerticesOffsets[i], this.BytesVerticesElementsCount[i]);

            this.IBO = new IndexBuffer(ShevaEngine.Instance.GraphicsDevice, IndexElementSize.ThirtyTwoBits, this.Indices.Count, BufferUsage.WriteOnly);
            this.IBO.SetData<Int32>(this.Indices.ToArray());
        }
    }
}
